package mgorm

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/dreamwood/jxgo/v1/mgorm/expr"
	"gitee.com/dreamwood/jxgo/v1/ss"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"time"
)

type Factory struct {
	Doc       Doc
	Ctx       context.Context
	Db        *mongo.Database
	DocConfig *DocConfig
	//事件开关
	IsEventOn bool

	//是否过滤删除
	IncludeDeleted bool
}

func NewFactory() *Factory {
	f := new(Factory)
	return f
}

func (this *Factory) SetDoc(doc Doc) {
	this.Doc = doc
}

func (this *Factory) Coll() *mongo.Collection {
	return this.DB().Collection(this.Doc.DocGetName())
}

func (this *Factory) SetCtx(ctx context.Context) {
	this.Ctx = ctx
}
func (this *Factory) GetCtx() context.Context {
	return this.Ctx
}
func (this *Factory) DB() *mongo.Database {
	if this.Db == nil {
		if db, ok := DBs[this.Doc.DocGetName()]; ok {
			this.Db = db
			return db
		}
	}
	return this.Db
}
func (this *Factory) SetDB(db *mongo.Database) {
	this.Db = db
}

func (this *Factory) Create(doc Doc) error {
	this.TriggerBeforeCreate(doc)
	//if doc.DocGetId() == 0 {
	//	doc.DocSetId(GetNextId(doc.DocGetName(), this.DB()))
	//}
	_, e := this.DB().Collection(doc.DocGetName()).InsertOne(this.Ctx, doc)
	if e != nil {
		return e
	}
	this.TriggerAfterCreate(doc)
	return nil
}
func (this *Factory) Update(doc Doc, value interface{}) error {
	this.TriggerBeforeUpdate(doc)
	rst, e := this.DB().Collection(doc.DocGetName()).
		UpdateOne(this.Ctx, bson.D{{"_id", doc.DocGetId()}}, bson.M{
			"$set": value})
	if e != nil {
		return e
	}
	if rst.ModifiedCount == 0 {
		return errors.New("没有可更新的文档")
	}
	if rst.ModifiedCount > 1 {
		return errors.New("没有可更新的文档")
	}
	this.TriggerAfterUpdate(doc)
	return nil
}
func (this *Factory) Replace(doc Doc) error {
	this.TriggerBeforeUpdate(doc)
	rst, e := this.DB().Collection(doc.DocGetName()).ReplaceOne(this.Ctx, bson.D{{"_id", doc.DocGetId()}}, doc)
	if e != nil {
		return e
	}
	if rst.ModifiedCount != 1 {
		return errors.New("未更新")
	}
	this.TriggerAfterUpdate(doc)
	return nil
}

func (this *Factory) Delete(doc Doc) error {
	this.TriggerBeforeDelete(doc)
	rst, e := this.DB().Collection(doc.DocGetName()).
		UpdateMany(this.Ctx, bson.D{{"_id", doc.DocGetId()}}, bson.M{"$set": bson.D{
			{"deleteAt", time.Now()},
		}})
	if e != nil {
		return e
	}
	if rst.ModifiedCount != 1 {
		return errors.New("删除失败")
	}
	this.TriggerAfterDelete(doc)
	return nil
}

func (this *Factory) UnDelete(doc Doc) error {
	this.TriggerBeforeUnDelete(doc)
	rst, e := this.DB().Collection(doc.DocGetName()).
		UpdateOne(this.Ctx, bson.D{{"_id", doc.DocGetId()}}, bson.M{"$set": bson.D{
			{"deleteAt", nil},
		}})
	if e != nil {
		return e
	}
	if rst.ModifiedCount != 1 {
		return errors.New("恢复失败")
	}
	this.TriggerAfterUnDelete(doc)
	return nil
}

func (this *Factory) Destroy(doc Doc) error {
	this.TriggerBeforeDestroy(doc)
	filter := bson.D{
		{"_id", doc.DocGetId()},
	}
	rst, e := this.DB().Collection(doc.DocGetName()).DeleteOne(this.Ctx, filter)
	if e != nil {
		return e
	}
	if rst.DeletedCount != 1 {
		return errors.New("未删除任何内容")
	}
	this.TriggerAfterDestroy(doc)
	return nil
}

func (this *Factory) FindManyBy(docs interface{}, query *Query) error {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.Size < 1 {
		query.Size = 0
	}
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	cursor, e := this.DB().Collection(this.Doc.DocGetName()).Find(
		this.Ctx,
		this.CreateFilters(query),
		Option(query.Size*query.Page-query.Size, query.Size, query.Order...),
	)
	if e != nil {
		return e
	}
	e = cursor.All(this.Ctx, docs)
	if e != nil {
		return e
	}
	return nil
}

func (this *Factory) UpdateManyBy(updateModel interface{}, query *Query) (int64, error) {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.Size < 1 {
		query.Size = 0
	}
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	rst, e := this.DB().Collection(this.Doc.DocGetName()).UpdateMany(
		this.Ctx,
		this.CreateFilters(query),
		updateModel,
		UpdateOptions(),
	)
	return rst.ModifiedCount, e
}

func (this *Factory) DeleteManyBy(query *Query) (int64, error) {
	updateModel := ss.Obj{
		"deleteAt": time.Now(),
	}
	return this.UpdateManyBy(updateModel, query)
}

func (this *Factory) DestroyManyBy(query *Query) (int64, error) {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.Size < 1 {
		query.Size = 0
	}
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	rst, e := this.DB().Collection(this.Doc.DocGetName()).DeleteMany(
		this.Ctx,
		this.CreateFilters(query),
	)
	return rst.DeletedCount, e
}
func (this *Factory) Count(query *Query) (int64, error) {
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	count, e := this.DB().Collection(this.Doc.DocGetName()).CountDocuments(
		this.Ctx,
		this.CreateFilters(query),
	)
	if e != nil {
		return -1, e
	}
	return count, nil
}

func (this *Factory) FindOneBy(doc Doc, query *Query) error {
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	//dev.PrintDebug(query.Filters)
	cursor := this.DB().Collection(doc.DocGetName()).FindOne(
		this.Ctx,
		this.CreateFilters(query),
		OptionOne(query.Order...),
	)
	e := cursor.Decode(doc)
	if e != nil {
		return e
	}
	return nil
}

func (this *Factory) FindId(doc Doc, id string) error {
	qb := NewQuery()
	qb.Filters["_id"] = bson.E{"$oid", id}
	return this.FindOneBy(doc, qb)
}

func (this *Factory) CreateFilters(query *Query) bson.D {
	filter := NewFilters()
	filter.SetDocConfig(this.DocConfig)
	filters := filter.CreateFilter(query.Filters)
	return filters
}

func (this *Factory) CreateSort(query *Query) bson.D {
	orderBys := bson.D{}
	for _, str := range query.Order {
		if str[0:1] == "-" {
			orderBys = append(orderBys, bson.E{str[1:], -1})
		} else {
			orderBys = append(orderBys, bson.E{str, 1})
		}
	}
	return orderBys
}
func (this *Factory) CreatePipeLine(query *Query) *PPL {
	ppl := NewPPL()
	filter := NewFilters()
	filter.SetDocConfig(this.DocConfig)
	filters := this.CreateFilters(query)
	//for _, dump := range query.Dumps {
	//	filter.PrepareJoins(dump)
	//}
	ppl.Dumps = query.Dumps
	filter.CreateJoins()
	for _, join := range filter.Joins {
		ppl.Add(join)
	}
	if len(filters) > 0 {
		ppl.AddMatch(filters)
	}
	return ppl
}

func (this *Factory) CreatePipeLineForCount(query *Query) *PPL {
	ppl := NewPPL()
	filter := NewFilters()
	filter.SetDocConfig(this.DocConfig)
	filters := this.CreateFilters(query)
	//for _, dump := range query.Dumps {
	//	filter.PrepareJoins(dump)
	//}

	filter.CreateJoins()
	for _, join := range filter.Joins {
		ppl.Add(join)
	}
	if len(filters) > 0 {
		ppl.AddMatch(filters)
	}
	return ppl
}

func (this *Factory) AggregateFind(docs interface{}, query *Query) error {
	if !this.IncludeDeleted && !query.IncludeDelete {
		query.Filters["deleteAt"] = nil
	}
	ppl := this.CreatePipeLine(query)
	ppl.AddSort(
		this.CreateSort(query),
	)
	if query.Page < 1 {
		query.Page = 1
	}
	if query.Size > 0 {
		ppl.AddSkip(int64(query.Size * (query.Page - 1)))
		ppl.AddLimit(int64(query.Size))
	}
	//重新添加需要数据挖掘的Dump
	dumpFilter := NewFilters()
	dumpFilter.SetDocConfig(this.DocConfig)
	for _, dump := range ppl.Dumps {
		dumpFilter.PrepareJoins(dump)
	}
	dumpFilter.CreateJoins()
	for _, join := range dumpFilter.Joins {
		ppl.Add(join)
	}
	ppl.AddSort(
		this.CreateSort(query),
	)
	c, e := this.DB().Collection(this.Doc.DocGetName()).Aggregate(this.Ctx, ppl.Get())
	if e != nil {
		return e
	}
	return c.All(this.Ctx, docs)
}

func (this *Factory) AggregateCount(query *Query) (int64, error) {
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	ppl := this.CreatePipeLineForCount(query)
	ppl.Add(bson.D{expr.Count("count")})
	c, e := this.DB().Collection(this.Doc.DocGetName()).Aggregate(this.Ctx, ppl.Get())
	if e != nil {
		return -1, e
	}
	wanted := make([]Counter, 0)
	e = c.All(this.Ctx, &wanted)
	if e != nil || len(wanted) == 0 {
		return -1, e
	}
	return wanted[0].Count, nil
}

func (this *Factory) AggregateSumInt(query *Query, field string) (int64, error) {
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	ppl := this.CreatePipeLineForCount(query)
	ppl.AddGroup(bson.D{
		{"_id", nil},
		expr.Sum("count", fmt.Sprintf("$%s", field)),
	})
	c, e := this.DB().Collection(this.Doc.DocGetName()).Aggregate(this.Ctx, ppl.Get())
	if e != nil {
		return -1, e
	}
	wanted := make([]Counter, 0)
	e = c.All(this.Ctx, &wanted)
	if e != nil || len(wanted) == 0 {
		return -1, e
	}
	return wanted[0].Count, nil
}

func (this *Factory) AggregateSumFloat(query *Query, field string) (float64, error) {
	if !this.IncludeDeleted {
		query.Filters["deleteAt"] = nil
	}
	ppl := this.CreatePipeLineForCount(query)
	ppl.AddGroup(bson.D{
		{"_id", nil},
		expr.Sum("count", fmt.Sprintf("$%s", field)),
	})
	c, e := this.DB().Collection(this.Doc.DocGetName()).Aggregate(this.Ctx, ppl.Get())
	if e != nil {
		return -1, e
	}
	wanted := make([]CounterFloat, 0)
	e = c.All(this.Ctx, &wanted)
	if e != nil || len(wanted) == 0 {
		return -1, e
	}
	return wanted[0].Count, nil
}

func (this *Factory) CreateMany(docs []Doc) error {
	//使用BulkWrite批量写入
	start := 0
	for {
		if start > len(docs) {
			break
		}
		end := start + 100
		if len(docs) < start+100 {
			end = len(docs)
		}
		batchWriteModels := docs[start:end]
		if len(batchWriteModels) == 0 {
			break
		}
		toWrite := make([]mongo.WriteModel, len(batchWriteModels))
		for i, model := range batchWriteModels {
			toWrite[i] = mongo.NewInsertOneModel().SetDocument(model)
		}
		_, e := this.DB().Collection(this.Doc.DocGetName()).BulkWrite(context.TODO(), toWrite)
		//dev.PrintDebugGo(rst)
		if e != nil {
			return e
		}
		start += 100
	}
	return nil

}
